
package Model;

import Controller.Controller;
import com.google.api.client.auth.oauth2.Credential;
import com.google.api.client.extensions.java6.auth.oauth2.AuthorizationCodeInstalledApp;
import com.google.api.client.extensions.java6.auth.oauth2.FileCredentialStore;
import com.google.api.client.extensions.jetty.auth.oauth2.LocalServerReceiver;
import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeFlow;
import com.google.api.client.googleapis.auth.oauth2.GoogleClientSecrets;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.jackson.JacksonFactory;
import com.google.api.services.drive.Drive;
import com.google.api.services.drive.Drive.Children;
import com.google.api.services.drive.Drive.Files;
import com.google.api.services.drive.DriveScopes;
import com.google.api.services.drive.model.ChildList;
import com.google.api.services.drive.model.ChildReference;
import com.google.api.services.drive.model.File;
import com.google.api.services.drive.model.FileList;
import com.google.api.services.drive.model.ParentReference;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
 * Klasa głowna modelu
 * @param drive - Drive API client.
 * @param HTTP_TRANSPORT - stała potrzebna do połączenia.
 * @param JSON_FACTORY - stała potrzebna do interpretowania jezyka JSON.
 * @param CLIENT_SECRECTS_PATH - scieżka do pliku z hasłami potrzebnych przy logowaniu.
 * @param CLIENT_SESSION_PATH - scieżka do pliku zawierającego dane sesji po autoryzacji.
 * @param CHUNK_SIZE - stała zawierająca ilość danych w bajtach, które pobieramy zanim aktualizujemy postęp.
 * @param credential - przechowuje dane sesji.
 */
public class Model {
  private static Drive drive;
  private static final HttpTransport HTTP_TRANSPORT = new NetHttpTransport();
  private static final JsonFactory JSON_FACTORY = new JacksonFactory(); 
  private static String CLIENT_SECRECTS_PATH = "client_secrets.json";
  private static String CLIENT_SESSION_PATH = "drive.json";
  private static String DOWNLOAD_PATH = System.getProperty("user.home");
  private static int CHUNK_SIZE = 100000;
  private Credential credential;
  
  public Model() {  
  }

  /**
   * Funkcja odpowiedzialna za autoryzacje.
   */
  public void doAuthorize() {
    
    GoogleClientSecrets clientSecrets;  
    FileCredentialStore credentialStore;
    GoogleAuthorizationCodeFlow flow;
    try{
      clientSecrets = GoogleClientSecrets.load( 
      JSON_FACTORY, new FileInputStream(CLIENT_SECRECTS_PATH) );   
    }
     catch(IOException | NullPointerException e ) {
      throw new RuntimeException("Nie można odczytać pliku sesji: " + CLIENT_SECRECTS_PATH);  
    }
    
    try{
    credentialStore = new FileCredentialStore(new java.io.File(
        System.getProperty("user.home"), CLIENT_SESSION_PATH), JSON_FACTORY);
    }
    catch(IOException | NullPointerException e ) {
      throw new RuntimeException("Nie można zapisać pliku sesji: " + CLIENT_SESSION_PATH );   
    }
    flow = new GoogleAuthorizationCodeFlow.Builder(
        HTTP_TRANSPORT, JSON_FACTORY, clientSecrets,
        Collections.singleton(DriveScopes.DRIVE)).setCredentialStore(credentialStore).build();
    try {


    credential = new AuthorizationCodeInstalledApp(flow, new LocalServerReceiver()).authorize("user");
    }
    catch(IOException | NullPointerException e ) {
     throw new RuntimeException("Auroyzacja nie powiodła się.");
    }
  }
  /**
   * Tworzy połączenie z dyskiem zdalnym.
   */
  public void buildService() {
        drive = new Drive.Builder(HTTP_TRANSPORT, JSON_FACTORY, credential).build();
  }
  /**
   * Kasuje podany plik z dysku internetowego.
   * @param fileName nazwa pliku
   */
  public void deleteFile(String fileName) {
      java.io.File f = new java.io.File(fileName);
       if(f.exists() && f.canWrite()) {
         f.delete();   
       }
  }
  /**
   * Zapisuje konfigurację do pliku FileOrganizer.conf w katalogu użytkownika.
   */
  public void updateConfigFile() {
      FileOutputStream config_file;
      PrintStream config_fileStream;
       try {
          config_file = new FileOutputStream (System.getProperty("user.home") + System.getProperty("file.separator") + "FileOrganizer.conf");
          config_fileStream = new PrintStream(config_file);
        }
        catch (FileNotFoundException e) { 
          throw new RuntimeException("Nie odnaleziono pliku konfiguracyjnego: "+System.getProperty("user.home") + System.getProperty("file.separator") + "FileOrganizer.conf");
        }
        config_fileStream.println(CLIENT_SECRECTS_PATH);
        config_fileStream.println(DOWNLOAD_PATH);
        config_fileStream.println(CHUNK_SIZE);
        config_fileStream.close();
      
  }
  /**
   *
   * @return scieżkę do pliku hasłami do autoryzacji.
   */
  public String getLoginInfoPath() {
        return CLIENT_SECRECTS_PATH;
  }
  /**
   * 
   * @return scieżkę do pliku z sesją.
   */  
  public String getCredentialsInfoPath() {
        return System.getProperty("user.home") + "/" + CLIENT_SESSION_PATH;
  }
  /**
   * 
   * @return stała zawierająca ilość danych w bajtach, które pobieramy zanim aktualizujemy postęp.
   */
  public int getChunkSize() {
        return CHUNK_SIZE;
  }
  /**
   * Wczytuje zawartość pliku konfiguracyjnego po uruchomieniu aplikacji.
   */
  public void setConfigValues() {
      FileInputStream config_file;
      BufferedReader result;
       try {
          config_file = new FileInputStream (System.getProperty("user.home") + System.getProperty("file.separator") + "FileOrganizer.conf");
          result = new BufferedReader(new InputStreamReader(config_file));
        }
        catch (FileNotFoundException e) { 
         throw new RuntimeException("Nie odnaleziono pliku konfiguracyjnego: "+System.getProperty("user.home") + System.getProperty("file.separator") + "FileOrganizer.conf");
        }
        try {
          String res;
          res = result.readLine(); 
          if(res != null && !res.equals("null")) {
                CLIENT_SECRECTS_PATH = res;
            }  
          res = result.readLine(); 
          if(res != null && !res.equals("null")) {
                DOWNLOAD_PATH = res;
            }
          res = result.readLine(); 
          if(res != null && !res.equals("null")) {
              if(Integer.valueOf(res) > 0)
                CHUNK_SIZE = Integer.valueOf(res);
            }
        }
        catch(IOException e) {
            throw new RuntimeException("Wystąpił błąd przy ustawieniu wartości konfiguracyjnych.");
        }
      
  }
  /**
   * Modyfikuje scieżkę do pliku z hasłami do autoryzacji.
   * @param filepath scieżka pliku
   */
  public void updateConfigPath(String filepath) {
        CLIENT_SECRECTS_PATH = filepath; 
        updateConfigFile();
  }
  /**
   * Modyfikuje scieżkę do katalogu w ktorym zostaną zapisane pliki.
   * @param filepath ścieżka katalogu
   */
  public void updateDownloadPath(String filepath) {
        DOWNLOAD_PATH = filepath; 
        updateConfigFile();
  }
  /**
   * Modyfikuje stałą zawierająca ilość danych w bajtach, które pobieramy zanim aktualizujemy postęp.
   * @param chunk_size chunk size w bajtach.
   */
  public void updateChunkSize(int chunk_size) {
        CHUNK_SIZE = chunk_size; 
        updateConfigFile();
  }
  /**
   * 
   * @return ścieżke do katalogu do którego pobieramy pliki;
   */
  public String GetDownloadPath() {
      return DOWNLOAD_PATH;
  }
  /**
   * Wysyla plik na zdalny dysk google.
   * @param file - referencja na wysylany plik.
   * @param parent_id - okresla parametr rodzica.
   * @param controller - referencja na kontroler.
   */
  public void insertFile(java.io.File file, String parent_id,Controller controller) {
    Upload u = new Upload(this,drive,file,parent_id,controller);
    u.start();
  }
  /**
   * Pobiera plik na dysk lokalny.
   * @param filename - nazwa pliku.
   * @param controller - referencja na kontroler. 
   */
  public void downloadFile(String filename,Controller controller) {
    Download d = new Download(this,drive,filename,controller);
    d.start();
  }
  
  /**
   * Zmienia nazwę pliku na dysku zdalnym.
   * @param fileId id pliku
   * @param newTitle nowa nazwa pliku
   */
  public void renameFile(String fileId, String newTitle) {
      File file = new File();
      file.setTitle(newTitle);
      try {
      Files.Patch patchRequest = drive.files().patch(fileId, file);
      patchRequest.setFields("title");

      File updatedFile = patchRequest.execute();
      }
      catch (IOException | NullPointerException e) {
          throw new RuntimeException("Nie udało się zmienić nazwy pliku na: "+ newTitle);
      }
  }
  /**
   * Kasuje plik przenosząc go do kosza.
   * @param fileId id pliku
   */
  public void trashFile(String fileId) {
    try {    
        drive.files().trash(fileId).execute();
    }
    catch(IOException | NullPointerException e) {
        throw new RuntimeException("Nie udało się usunąć pliku.");
    } 
  }
  /**
   * Tworzy folder na dysku zdalnym.
   * @param title - nazwa folderu
   * @param parent_id - id folderu rodzica
   */
  public void insertFolder(String title, String parent_id) {
    List<ParentReference> parents = null;
    File body = new File();
    if(parent_id != null) {    
        ParentReference newParent = new ParentReference();
        newParent.setId(parent_id);
        parents = new ArrayList<>();
        parents.add(newParent);   
    }
   body.setParents(parents);
   body.setTitle(title);
   body.setMimeType("application/vnd.google-apps.folder");
   try {
   File file = drive.files().insert(body).execute();
   }
   catch(IOException | NullPointerException e) {
       throw new RuntimeException("Nie udało się utworzyć folderu: "+title);
   } 
  }
  /**
   * Pobiera listę plików z dysku zdalnego.
   * @return listę plików
   */
  public List<File> getFilesList() {
      List<File> result = new ArrayList<>();
      try {
      Files.List request = drive.files().list();
 
      do {
           FileList files = request.execute();
           result.addAll(files.getItems());
           request.setPageToken(files.getNextPageToken());
             
         } while (request.getPageToken() != null &&
             request.getPageToken().length() > 0);      
    return result;  
      }
      catch(IOException | NullPointerException e) {
          throw new RuntimeException("Nie udało się pobrać listy plików.");
      }
  }
 /**
  * Pobiera wszystkie pliki i foldery znajdujące się w danym folderze.
  * @param folder_id id folderu
  */
 public void getFolderChildren(String folder_id) {
    try {
    Children.List request = drive.children().list(folder_id);
    do {
        ChildList children = request.execute();
        for (ChildReference child : children.getItems()) {
          System.out.println("File Id: " + child.getId());
        request.setPageToken(children.getNextPageToken());
        
        } 
       }
        while (request.getPageToken() != null &&
             request.getPageToken().length() > 0);   
    } catch(IOException | NullPointerException e) {
        throw new RuntimeException("Nie udało się pobrać danych z folderu.");
    }
 }
 /**
  * Pobiera id pliku o zadanej nazwie.
  * @param filename nazwa pliku
  * @return id pliku
  */
 public String GetFileID(String filename) {
     
 List<File> results = null;
  try{
    results = this.getFilesList();
  }
  catch (NullPointerException e) {
    throw new RuntimeException("Nie udało się pobrać id pliku: "+filename);
  }
  for (File f : results) {
    if(f.getTitle().equals(filename)) {
       return f.getId();
    }
  }   
  return null;
 } 
    
}
